#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define MYKEY_CNT 		  1		  	/* 设备号个数 */
#define KEY_NAME	 	"mykey" 	/* 名字 */
#define KEY0VALUE 		 0X01		/* KEY0 按键值 */
#define INVAKEY 		 0XFF		/* 无效的按键值 */
#define KEY_NUM 		  1			/* 按键数量 */

/* 中断 IO 描述结构体 */
typedef struct IrqKey
{
	int iGpio;							 	/* gpio */
	int iIrqNum;							/* 中断号 */
	unsigned char ucKeyValue;				/* 按键对应的键值 */
	char name[10];						 	/* 名字 */
	irqreturn_t (*handler)(int, void *); 	/* 中断服务函数 */
}T_IrqKey,*PT_IrqKey;

/* imx6uirq 设备结构体 */
typedef struct KeyDevice
{
	dev_t DevID;							/* 设备号 */
	struct cdev tCharDev;					/* cdev */
	struct class *ptClass;					/* 类 */
	struct device *ptDevice;				/* 设备 */
	int iMajor;								/* 主设备号 */
	int iMinor;								/* 次设备号 */
	struct device_node *ptDeviceNode;		/* 设备节点 */
	atomic_t keyvalue;						/* 有效的按键键值 */
	atomic_t releasekey;					/* 标记是否完成一次完成的按键*/
	struct timer_list tTimer;				/* 定义一个定时器*/
	T_IrqKey tIrqKey[KEY_NUM]; 		/* 按键描述数组 */
	unsigned char ucCurKeyNum;				/* 当前的按键号 */
}T_KeyDevice,*PT_KeyDevice;

T_KeyDevice g_tMyKey; 

static irqreturn_t Key0Handler(int irq, void *dev_id)
{
	PT_KeyDevice dev = (PT_KeyDevice)dev_id;
	dev->ucCurKeyNum = 0;
	dev->tTimer.data = (volatile long)dev_id;
	mod_timer(&dev->tTimer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */

	return IRQ_RETVAL(IRQ_HANDLED);
}

static void TimerFunction(unsigned long arg)
{
	unsigned char ucValue;
	unsigned char ucNum;
	PT_IrqKey ptIrqKey;
	PT_KeyDevice dev = (PT_KeyDevice) arg;

	ucNum = dev->ucCurKeyNum;

	ptIrqKey = &dev->tIrqKey[ucNum];

	ucValue = gpio_get_value(ptIrqKey->iGpio); 		/* 读取IO值 */
	if (ucValue == 0)                             	/* 按下按键 */
	{
		atomic_set(&dev->keyvalue, ptIrqKey->ucKeyValue);
	}
	else             							 	/* 按键松开 */
	{
		atomic_set(&dev->keyvalue, 0x80 | ptIrqKey->ucKeyValue);
		atomic_set(&dev->releasekey, 1);			 /* 标记松开按键，即完成一次完整的按键过程 */
	}
}

static ssize_t KeyDeviceRead(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret = 0;
	unsigned char keyvalue = 0;
	unsigned char releasekey = 0;
	PT_KeyDevice dev = (PT_KeyDevice)filp->private_data;

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);
	if (releasekey)						/* 有按键按下 */
	{ 
		if (keyvalue & 0x80)
		{
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		}
		else
		{
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
	}
	else
	{
		goto data_error;
	}
	return 0;

data_error:
	return -EINVAL;
}

static int KeyDeviceOpen(struct inode *inode, struct file *filp)
{
	filp->private_data = &g_tMyKey;
	return 0;
}
static struct file_operations tMyKeyFileOps = {
	.owner = THIS_MODULE,
	.open = KeyDeviceOpen,
	.read = KeyDeviceRead,
};

//驱动入口函数
static int __init irqkey_init(void)
{
	int ret;
	/*分配设备号*/
	if(g_tMyKey.iMajor)    /*指定了设备号*/
	{
		g_tMyKey.DevID = MKDEV(g_tMyKey.iMajor,0);
		register_chrdev_region(g_tMyKey.DevID,MYKEY_CNT,KEY_NAME);
	}
	else         /*未指定设备号*/
	{
		alloc_chrdev_region(&g_tMyKey.DevID,0,MYKEY_CNT,KEY_NAME);
		g_tMyKey.iMajor = MAJOR(g_tMyKey.DevID);
		g_tMyKey.iMinor = MINOR(g_tMyKey.DevID);
	}
	printk("KEY major=%d,minor=%d\r\n",g_tMyKey.iMajor, g_tMyKey.iMinor);

	/*注册字符设备*/
	g_tMyKey.tCharDev.owner = THIS_MODULE;

	cdev_init(&g_tMyKey.tCharDev,&tMyKeyFileOps);
	cdev_add(&g_tMyKey.tCharDev, g_tMyKey.DevID, MYKEY_CNT);

	/*附加信息，自动创建节点*/
	/*1、创建类*/
	g_tMyKey.ptClass = class_create(THIS_MODULE,KEY_NAME);
	if (IS_ERR(g_tMyKey.ptClass)) 
	{
		return PTR_ERR(g_tMyKey.ptClass);
	}
	/*2、创建设备*/
	g_tMyKey.ptDevice = device_create(g_tMyKey.ptClass, NULL, g_tMyKey.DevID, NULL, KEY_NAME);
	if (IS_ERR(g_tMyKey.ptDevice)) 
	{
		return PTR_ERR(g_tMyKey.ptDevice);
	}
	/*读取设备树信息*/
	/*1、获取设备树节点*/
	g_tMyKey.ptDeviceNode = of_find_node_by_path("/mykey");
	if(g_tMyKey.ptDeviceNode == NULL) {
		printk("mykey node not find!\r\n");
		return -EINVAL;
	} else {
		printk("mykey node find!\r\n");
	}
	/*2、获取GPIO*/
	g_tMyKey.tIrqKey[0].iGpio = of_get_named_gpio(g_tMyKey.ptDeviceNode, "key-gpio", 0);
	if (g_tMyKey.tIrqKey[0].iGpio < 0)
	{
		printk("can't get key-gpio");
		return -EINVAL;
	}
	printk("key-gpio%d num = %d\r\n", 0, g_tMyKey.tIrqKey[0].iGpio);

	memset(g_tMyKey.tIrqKey[0].name,0x00,sizeof(g_tMyKey.tIrqKey[0].name));
	sprintf(g_tMyKey.tIrqKey[0].name,"key0");
	g_tMyKey.tIrqKey[0].name[9] = '\0';
	/*3、设置GPIO功能*/

	gpio_request(g_tMyKey.tIrqKey[0].iGpio,g_tMyKey.tIrqKey[0].name);
	gpio_direction_input(g_tMyKey.tIrqKey[0].iGpio); 

	/*4、获得中断号，申请中断，注册中断处理函数*/

	g_tMyKey.tIrqKey[0].iIrqNum =  irq_of_parse_and_map(g_tMyKey.ptDeviceNode,0); /*解析设备树中的中断消息*/

	//g_tMyKey.tIrqKey[0].iIrqNum =  gpio_to_irq(g_tMyKey.tIrqKey[0].iGpio);        /*将指定GPIO转换为irq*/
	g_tMyKey.tIrqKey[0].handler    = Key0Handler;
	g_tMyKey.tIrqKey[0].ucKeyValue = KEY0VALUE;

	ret = request_irq(g_tMyKey.tIrqKey[0].iIrqNum,g_tMyKey.tIrqKey[0].handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,g_tMyKey.tIrqKey[0].name,&g_tMyKey);
	if (ret < 0)
	{
		printk("irq %d request failed!\r\n",g_tMyKey.tIrqKey[0].iIrqNum);
		return -EFAULT;
	}

	/*5、定时器初始化*/

	init_timer(&g_tMyKey.tTimer);
	g_tMyKey.tTimer.function = TimerFunction;

	/*6、初始化按键*/

	atomic_set(&g_tMyKey.keyvalue, INVAKEY);
	atomic_set(&g_tMyKey.releasekey, 0);

	return 0;
}
static void __exit irqkey_exit(void)
{

	del_timer_sync(&g_tMyKey.tTimer);        /*删除timer*/

	free_irq(g_tMyKey.tIrqKey[0].iIrqNum,&g_tMyKey);

	gpio_free(g_tMyKey.tIrqKey[0].iGpio);

	cdev_del(&g_tMyKey.tCharDev);/*  删除cdev */

	unregister_chrdev_region(g_tMyKey.DevID, MYKEY_CNT); /* 注销设备号 */

	device_destroy(g_tMyKey.ptClass, g_tMyKey.DevID);
	class_destroy(g_tMyKey.ptClass);
}

module_exit(irqkey_exit);
module_init(irqkey_init);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("d1023");